import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

class StringContainer
{
    String label;
    int len;
    String value;
    BasicLit lit;
}

class SymbolTable
{
    Map<Id, Object> types = new HashMap<>();
    Map<Id, Object> vars = new HashMap<>();
}

public class PackageManager
{
    public List<AstTree> trees;
    private String dir;
    private Scope packageScope;
    public Set<String> denpendency;
    public List<ScopeObject> packageObjects;
    public List<Decl> decls;
    public List<Id> packageIds;
    boolean isStandard;
    public SymbolTable symbolTable;
    public List<TypeDecl> types = new ArrayList<>();
    public List<ValueDecl> vars = new ArrayList<>();
    public List<String> files;
    public List<Function> functions = new ArrayList<>();
    Id name;
    public int stringIndex = 0;
    public List<StringContainer> strings = new ArrayList<>();

    public PackageManager()
    {
        trees = new ArrayList<>();
        packageObjects = new ArrayList<>();
        decls = new ArrayList<>();
        packageIds = new ArrayList<>();
        files = new ArrayList<>();
        denpendency = new HashSet<>();
    }

    public PackageManager(String dir)
    {
        trees = new ArrayList<>();
        packageObjects = new ArrayList<>();
        decls = new ArrayList<>();
        packageIds = new ArrayList<>();
        files = new ArrayList<>();
        symbolTable = new SymbolTable();
        denpendency = new HashSet<>();
        try
        {
            setDir(dir);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public void addString(StringContainer stringContainer)
    {
        strings.add(stringContainer);
    }

    public void setDir(String dir) throws IOException
    {
        this.dir = dir;
        File file = new File(dir);
        if(file.isDirectory())
        {
            String[] list = file.list();
            for(String fileName : list)
            {
                String absolutePath = dir + "/" + fileName;
                File file1 = new File(absolutePath);
                if(file1.isDirectory())
                    continue;
                else if(absolutePath.endsWith(".go"))
                {
                    files.add(absolutePath);
                }
            }
        }
        else
            System.out.println("不是目录");
    }

    public void buildAst() throws IOException
    {
        File file = new File(dir);
        String[] list = file.list();
        for(String fileName : list)
        {
            String absolutePath = dir + "/" + fileName;
            File file1 = new File(absolutePath);
            if(file1.isDirectory())
                continue;
            else if(absolutePath.endsWith(".go"))
            {
                AstTree astTree = getAst(absolutePath);
                astTree.setPath(absolutePath);
                astTree.printTree();
                name = new Id(astTree.packageName);
                trees.add(astTree);
                System.out.println(absolutePath);
            }
        }
    }

    public Set<String> resolveDependency(Set<String> init) throws IOException
    {
        Set<String> list = getDirectDenpendency();
        for(String importPath : list)
        {
            if(init.contains(importPath))
                continue;
            String path = getDenpendencyPath(importPath);
            if(!path.equals(""))
            {
                PackageManager packageManager = new PackageManager(path);
                init.add(importPath);
                packageManager.resolveDependency(init);
                init.addAll(packageManager.getDependency());
            }
        }
        denpendency = init;
        return init;
    }

    public void solveUnresolveSymbol(Scope globalScope) throws IOException
    {
        packageScope = new Scope(globalScope);
        List<ScopeObject> ids = getResolvedSymbol();
        for(String s : denpendency)
        {
            String path = getDenpendencyPath(s);
            PackageManager packageManager = new PackageManager(path);
            packageManager.buildAst();
            ids.addAll(packageManager.getResolvedSymbol());
        }
        for(ScopeObject object : ids)
            packageScope.insert(object);

        for(AstTree tree : trees)
        {
            resolveImports(tree);
            List<Id> unresolve = new ArrayList<>();
            for(Id id : tree.unresolve)
            {
                id.object = packageScope.find(id.name);
                if(id.object == null)
                {
                    id.object = globalScope.find(id.name);
                    if(id.object == null)
                    {
                        System.out.println("未解析id: " + id.name);
                        unresolve.add(id);
                    }
                }
            }
            tree.unresolve = unresolve;
            decls.addAll(tree.decls);
        }
    }

    public void resolveImports(AstTree tree)
    {
        Map<String, Boolean> map = new HashMap<>();
        for(Import _import : tree.imports)
        {
            for(String path : _import.pathSpec)
            {
                path = path.substring(1, path.length()-1);
                int index = path.lastIndexOf('/');
                if(index != -1)
                    path = path.substring(index+1);
                map.put(path, true);
            }
        }

        List<Id> unresolve = new ArrayList<>();
        for(Id id : tree.unresolve)
        {
            if(map.containsKey(id.name))
            {
                id.object = new ScopeObject(null, ScopeObject.KIND.Pkg, id.name);
                packageIds.add(id);
            }
            else
                unresolve.add(id);
        }
        tree.unresolve = unresolve;
    }

    public List<ScopeObject> getResolvedSymbol()
    {
        List<ScopeObject> ids = new ArrayList<>();
        for(AstTree tree : trees)
            ids.addAll(tree.rootScope.objects.values());
        return ids;
    }

    public Set<String> getDependency()
    {
        return denpendency;
    }

    public Set<String> getDirectDenpendency() throws IOException
    {
        Set<String> ans = new HashSet<>();
        for(String file : files)
        {
            List<Import> imports = getImports(file);
            for(Import _import : imports)
            {
                for(String path : _import.pathSpec)
                {
                    ans.add(path.substring(1, path.length()-1));
                }
            }
        }
        return ans;
    }

    private List<Import> getImports(String file) throws IOException
    {
        System.out.println(file);
        InputStream cymbol = new FileInputStream(new File(file));
        ANTLRInputStream aInputStream = new ANTLRInputStream(cymbol);
        GoLexer lexer = new GoLexer(aInputStream);
        CommonTokenStream cts = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(cts);
        ImportVisitor importVisitor = new ImportVisitor();
        List<Import> ans = utils.typeConversion(importVisitor.visitSourceFile(parser.sourceFile()), Import.class);
        cymbol.close();
        return ans;
    }

    private AstTree getAst(String path) throws IOException
    {
        InputStream cymbol = new FileInputStream(new File(path));
        ANTLRInputStream aInputStream = new ANTLRInputStream(cymbol);
        GoLexer lexer = new GoLexer(aInputStream);
        CommonTokenStream cts = new CommonTokenStream(lexer);
        GoParser parser = new GoParser(cts);
        AstVisitor astVisitor = new AstVisitor();
        AstTree astTree = (AstTree)astVisitor.visitSourceFile(parser.sourceFile());
        cymbol.close();
        return astTree;
    }

    public static String getDenpendencyPath(String path)
    {
        Path path1 = Paths.get(Configs.GoPath, path);
        File file = path1.toFile();
        if(file.exists())
            return file.getAbsolutePath();
        Path path2 = Paths.get(Configs.ProjectPath, path);
        file = path2.toFile();
        if(file.exists())
            return file.getAbsolutePath();
        System.out.println("依赖不存在: " + file.getAbsolutePath());
        return "";
    }
}
